#coding=utf-8
# revision_config_window.py - Modal Window to configure revision panel.
#
# Copyright 2011 Paul Gluchow <pgsurp@googlemail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 3.

from modal_window import ModalWindow
import curses.ascii


class RevisionConfigWindow(ModalWindow):
    """
    modal window class for setting config options to current revision list
    
    option:
        An option has a name, a value, an information text for user
        and max length, used as text field.
        Options are sent by the caller in a list.
        These options are used for printing the modal window.
        
    result-dictionary:
        Result dictionary is used to send the modified option values back
        to the caller class.
        Result dictionary has key:value pairs, where
        key - option name (same as caller class set) (String)
        value - value of text fields (String)
    
    """
    _PREFERRED_MIN_WIDTH = 40
    _PREFERRED_MIN_HEIGHT = 20
    _MIN_WIDTH = 30
    _MIN_HEIGHT = 9
    _message_visible = True
    _EXIT_BUTTON = u'Cancel'
    _title = _(u'Revision Configuration')
    
    # pad where config options are printed
    __pad = None
    
    # height of visible part of the pad (needed for refresh())
    __pad_h = 0
    # width  of visible part of the pad (needed for refresh())
    __pad_w = 0
    
    # y cursor position in pad
    __y_pos_pad = 0
    # x cursor position in pad
    __x_pos_pad = 0
    
    # index of selected option or button
    __selected_index = 0
    # coordinates of ok button
    __ok_btn_x = 0
    __ok_btn_y = 0
    
    # coordinates of cancel button
    __cancel_btn_x = 0
    __cancel_btn_y = 0
    
    __OK = u'<' + _(u'OK') + u'>'
    __CANCEL = u'<' + _(u'Cancel') + u'>'
    
    # the name of options
    __option = []
    __optionname_max_width = 0
    __option_tf = [] # list of configs from config textfields
    __option_beginX = 0 # x position where config textfields begin
    __option_cursor_pos = [] # List of current cursor positions for each config option
    __option_tf_max = 20 # max symbols

    # offset where options begin in the pad
    __option_offsetX = 1
    __option_offsetY = 1
    
    # list of configurations with preallocated values for text fields,
    # max length of text field and information about them
    # structure of each element in list:
    # [option_name, preallocated_option_value, option_information_for_user, max_length]
    __configs_list = None
    
    
    def __init__(self, configs_list):
        self.__configs_list = configs_list
        self.__y_pos_rev_pad = 0
        self.__x_pos_rev_pad = 0
        self.__option = []
        self.__option_tf = []
        self.__option_cursor_pos = []
        
        # initialize the option names and values, got from caller
        for conf in configs_list:
            self.__option.append(conf[0])
            self.__option_tf.append(conf[1])
            self.__option_cursor_pos.append(len(conf[1]))
            if self.__optionname_max_width < len(conf[0]):
                self.__optionname_max_width = len(conf[0])

            

    def show(self, window, main_win):
        """
        Function to draw modal window

        @param: 
            window - window where modal window is printed
        """
        self.__pad = curses.newpad(1, 1)
        ModalWindow.show(self, window, main_win)


    def handle_keypress(self, ch, uni):
        """
        function is handling the pressed keys

        @param:
            ch - code of pressed key
            uni - ch in unicode

        if pressed key is closing the modal window,
        there are results returned to the caller as a dictionary.
        @return: {key, value}
            key = 'button' or name of option
            value = name of pressed button or set option in text field
        """
        
        result = ModalWindow.handle_keypress(self, ch, uni)
        if result is not None:
            return result
        
        sel_index = self.__selected_index
                
        # Key Tab: switch to next selected element
        if ch == ord('\t'):
            self.__switch_select()
        
        # Key Enter: exiting the modal window with result-dictionaries as return
        elif ch == ord('\n'):
            if sel_index == len(self.__option):
                result = {u'button': u'OK'}
                for i in range(len(self.__option)):
                    result[self.__option[i].lower()] = self.__option_tf[i]
                # @ return:
                # result structure: {'button': 'OK', 'user': 'user@xyz.com', 'revision': '2', ... }
                return result
            
            elif sel_index == len(self.__option) + 1:
                return {u'button': u'Cancel'}
            
            # switch active text field by releasing enter key
            elif sel_index >= 0:
                self.__switch_select()
        
        # if a text field is selected
        elif sel_index >= 0 and sel_index < len(self.__option):
            # if a symbol is typed, it is inserted to the current position (cursor position)
            # and finally the cursor is moved to next position
            if uni or curses.ascii.isprint(ch):
                curser_pos = self.__option_cursor_pos[sel_index]
                tf_len = len(self.__option_tf[sel_index])
                
                # maximum size of text field to be inside the pad
                self.__option_tf_max = self.__configs_list[sel_index][3]
                
                # only if there is space for symbol to be inserted, it will be done
                if curser_pos < self.__option_tf_max and tf_len < self.__option_tf_max:
                    self.__option_tf[sel_index] = self.__option_tf[sel_index][:curser_pos] + \
                        unichr(ch) + self.__option_tf[sel_index][curser_pos:]
                    self.__draw_options()
                    self.__curs_move(1)
            
            # Key Left: (try to) move cursor by one to the previous symbol
            elif ch == curses.KEY_LEFT:
                self.__curs_move(-1)
            
            # Key Right: (try to) move cursor by one to the next symbol
            elif ch == curses.KEY_RIGHT:
                self.__curs_move(1)
            
            # Key Backspace: (try to) delete the symbol on the left side to the cursor position
            # and, if successful, moves to previous cursor if
            elif ch == curses.KEY_BACKSPACE:
                if self.__option_cursor_pos[sel_index] > 0:
                    curser_pos = self.__option_cursor_pos[sel_index]
                    self.__option_tf[sel_index] = self.__option_tf[sel_index][:curser_pos - 1] + \
                                     self.__option_tf[sel_index][curser_pos:]
                    self.__draw_options()
                    self.__curs_move(-1)

            # Key Delete: (try to) delete the symbol at the current cursor position
            # cursor position doesn't change
            elif ch == curses.KEY_DC:
                curser_pos = self.__option_cursor_pos[sel_index]
                self.__option_tf[sel_index] = self.__option_tf[sel_index][:curser_pos] + \
                                 self.__option_tf[sel_index][curser_pos + 1:]
                self.__draw_options()
                
    def __switch_select(self):
        """
        function switches between active text fields and buttons
        """
        if self.__selected_index == len(self.__option) + 1:
            self.__selected_index = 0
        else:
            self.__selected_index += 1
        self.__draw_cursor()
            

    
    def __curs_move(self, move_x):
        """
        function moves the cursor inside the text field and remember the current position
        
        @param: move_x - x symbols the cursor should moved to the left (-x) or the the right (+x)
        """
        
        # height and width of modal window
        height, width = self._window.getmaxyx()
        
        curs_pos = self.__option_cursor_pos[self.__selected_index]
        current_config_len = len(self.__option_tf[self.__selected_index])
        
        if move_x < 0 and curs_pos > 0:
            curs_pos += move_x
            #self.__x_pos_pad -= 1
            
        elif move_x > 0 and curs_pos < width - 2 and \
                    curs_pos < current_config_len:
            curs_pos += move_x
            #self.__x_pos_pad += 1
            
        self.__option_cursor_pos[self.__selected_index] = curs_pos
        
        #self.__x_pos_pad = self.__option_cursor_pos[self.__selected_index]
        
        self.__draw_cursor()
        
    
    
    def __draw_cursor(self):
        """
        function to draw the current position of blinking cursor
        and to highlight the buttons when they are selected
        """
        # begin coordinates
        y, x = self._window.getparyx()

        sel_index = self.__selected_index
        
        curses.curs_set(1)
        
        # if ok button is selected, highlight it
        if sel_index == len(self.__option):
            curses.curs_set(0)
            self._main_win.chgat(y + self.__ok_btn_y, x + self.__ok_btn_x - 1,
                                 len(self.__OK) + 2, curses.A_REVERSE)
        
        # if cancel button is selected, highlight it and unhighlight ok button
        elif sel_index == len(self.__option) + 1:
            curses.curs_set(0)
            self._main_win.chgat(y + self.__cancel_btn_y, x + self.__cancel_btn_x - 1,
                                 len(self.__CANCEL) + 2, curses.A_REVERSE)
            self._main_win.chgat(y + self.__ok_btn_y, x + self.__ok_btn_x - 1,
                                 len(self.__OK) + 2, curses.A_NORMAL)
            
        # if text field is selected
        elif sel_index >= 0:
            # if first option is selected, unhighlight Cancel Button
            if sel_index == 0:
                self._main_win.chgat(y + self.__cancel_btn_y, x + self.__cancel_btn_x - 1,
                                 len(self.__CANCEL) + 2, curses.A_NORMAL)
            # move cursor to current option position
            self._main_win.move(y + 2 + sel_index + self.__option_offsetY,
                                x + 2 + self.__option_beginX + self.__option_cursor_pos[sel_index])
            
        self.__upd()
        

    def resize(self, window):
        """
        Function to change the size of the window dynamically
        
        First the buttons are set to correct place depending on the size in the subwindow.
        When the name of subwindow is printed.
        Finally calling the refresh-function from parent class
        and drawing the current cursor position and the options text fields

        @param:
            window - window where modal window is printed
        """
        
        ModalWindow.resize(self, window)
        # if the window where modal window should be printed is too small,
        # no modal window will be shown
        if not self._message_visible:
            return
  
        # begin coordinates
        y, x = self._window.getparyx()
        # height and width of modal window
        height, width = self._window.getmaxyx()
        
        # coordinates of ok button
        self.__ok_btn_x = width / 4 - len(self.__OK) / 2
        self.__ok_btn_y = height - 2
    
        # coordinates of cancel button
        self.__cancel_btn_x = width * 3 / 4 - len(self.__CANCEL) / 2
        self.__cancel_btn_y = height - 2
        
        self._window.addstr(self.__ok_btn_y, self.__ok_btn_x, self._main_window.str_to_external(self.__OK))
        self._window.addstr(self.__cancel_btn_y, self.__cancel_btn_x, self._main_window.str_to_external(self.__CANCEL))
        
        # config subwindow, for all config options
        config_sw = self._window.subwin(height - 3, width - 2, y + 1, x + 1)
        config_sw.box()
        config_sw.addnstr(0, 1, self._main_window.str_to_external(_(u'config')), width - 2)

        self._window.refresh()
        
        self.__draw_cursor()
        
        self.__draw_options()


    def __upd(self):
        """
        function updates the pad (without changing any content)
        """
        
        height, width = self._window.getmaxyx() # height and width of modal window
        y, x = self._window.getparyx() # begin coordinates
        
        self.__pad_h = y + height - 4
        self.__pad_w = x + width - 4
        
        self.__pad.refresh(self.__y_pos_pad, self.__x_pos_pad, y + 2, x + 2,
                            self.__pad_h, self.__pad_w)


    def __draw_options(self):
        """
        Function to draw the table of option names, the
        option values (in text fields) using max_length and
        the information to each options for user.
        
        All contents are drawn in one pad.
        """
        
        self.__pad.clear()
        
        #TODO dynamically calculate the size
        pad_h = 20
        pad_w = 100
        self.__pad.resize(pad_h, pad_w)
        
        # distance between option name and the text fields
        tab = 3
        # begin of all text fields
        self.__option_beginX = self.__option_offsetX + self.__optionname_max_width + tab
        
        # print options with their text fields (values) and information for user
        for i in range(len(self.__option)):
            max_len = self.__configs_list[i][3]
            self.__pad.addstr(i + self.__option_offsetY, self.__option_offsetX,
                                self._main_window.str_to_external(self.__option[i]))
            self.__pad.addstr(i + self.__option_offsetY, self.__option_beginX,
                                self._main_window.str_to_external(self.__option_tf[i]))
            self.__pad.chgat(i + self.__option_offsetY, self.__option_beginX,
                                    max_len, curses.A_UNDERLINE)
            self.__pad.addstr(i + self.__option_offsetY, self.__option_beginX + max_len + 1,
                                self._main_window.str_to_external(self.__configs_list[i][2]))
        
        self.__draw_cursor()
        self.__upd()        
        